Emberveil sourcegame/2006Scape Server/src/main/java/org/apollo/net/codec/login/LoginDecoder.java
package org.apollo.net.codec.login;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import java.math.BigInteger;
import java.net.InetSocketAddress;
import java.security.SecureRandom;
import java.util.List;
import java.util.logging.Logger;
import org.apollo.cache.FileSystemConstants;
import org.apollo.net.NetworkConstants;
import org.apollo.util.BufferUtil;
import org.apollo.util.StatefulFrameDecoder;
import org.apollo.util.security.IsaacRandom;
import org.apollo.util.security.IsaacRandomPair;
import org.apollo.util.security.PlayerCredentials;
import com.google.common.net.InetAddresses;
/**
* A {@link StatefulFrameDecoder} which decodes the login request frames.
*
* @author Graham
*/
public final class LoginDecoder extends StatefulFrameDecoder<LoginDecoderState> {
/**
* The logger for this class.
*/
private static final Logger logger = Logger.getLogger(LoginDecoder.class.getName());
/**
* The secure random number generator.
*/
private static final SecureRandom RANDOM = new SecureRandom();
/**
* The login packet length.
*/
private int loginLength;
/**
* The reconnecting flag.
*/
private boolean reconnecting;
/**
* The server-side session key.
*/
private long serverSeed;
/**
* The username hash.
*/
private int usernameHash;
/**
* Creates the login decoder with the default initial state.
*/
public LoginDecoder() {
super(LoginDecoderState.LOGIN_HANDSHAKE);
}
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out, LoginDecoderState state) {
switch (state) {
case LOGIN_HANDSHAKE:
decodeHandshake(ctx, in, out);
break;
case LOGIN_HEADER:
decodeHeader(ctx, in, out);
break;
case LOGIN_PAYLOAD:
decodePayload(ctx, in, out);
break;
default:
throw new IllegalStateException("Invalid login decoder state: " + state);
}
}
/**
* Decodes in the handshake state.
*
* @param ctx The channel handler context.
* @param buffer The buffer.
* @param out The {@link List} of objects to pass forward through the pipeline.
*/
private void decodeHandshake(ChannelHandlerContext ctx, ByteBuf buffer, List<Object> out) {
if (buffer.isReadable()) {
usernameHash = buffer.readUnsignedByte();
serverSeed = RANDOM.nextLong();
ByteBuf response = ctx.alloc().buffer(17);
response.writeByte(LoginConstants.STATUS_EXCHANGE_DATA);
response.writeLong(0);
response.writeLong(serverSeed);
ctx.channel().write(response);
setState(LoginDecoderState.LOGIN_HEADER);
}
}
/**
* Decodes in the header state.
*
* @param ctx The channel handler context.
* @param buffer The buffer.
* @param out The {@link List} of objects to pass forward through the pipeline.
*/
private void decodeHeader(ChannelHandlerContext ctx, ByteBuf buffer, List<Object> out) {
if (buffer.readableBytes() >= 2) {
int type = buffer.readUnsignedByte();
if (type != LoginConstants.TYPE_STANDARD && type != LoginConstants.TYPE_RECONNECTION) {
logger.fine("Failed to decode login header.");
writeResponseCode(ctx, LoginConstants.STATUS_LOGIN_SERVER_REJECTED_SESSION);
return;
}
reconnecting = type == LoginConstants.TYPE_RECONNECTION;
loginLength = buffer.readUnsignedByte();
setState(LoginDecoderState.LOGIN_PAYLOAD);
}
}
/**
* Decodes in the payload state.
*
* @param ctx The channel handler context.
* @param buffer The buffer.
* @param out The {@link List} of objects to pass forward through the pipeline.
*/
private void decodePayload(ChannelHandlerContext ctx, ByteBuf buffer, List<Object> out) {
if (buffer.readableBytes() >= loginLength) {
ByteBuf payload = buffer.readBytes(loginLength);
int version = 255 - payload.readUnsignedByte();
int release = payload.readUnsignedShort();
int memoryStatus = payload.readUnsignedByte();
if (memoryStatus != 0 && memoryStatus != 1) {
logger.fine("Login memoryStatus (" + memoryStatus + ") not in expected range of [0, 1].");
writeResponseCode(ctx, LoginConstants.STATUS_LOGIN_SERVER_REJECTED_SESSION);
return;
}
boolean lowMemory = memoryStatus == 1;
int[] crcs = new int[FileSystemConstants.ARCHIVE_COUNT];
for (int index = 0; index < 9; index++) {
crcs[index] = payload.readInt();
}
int length = payload.readUnsignedByte();
if (length != loginLength - 41) {
logger.fine("Login packet unexpected length (" + length + ")");
writeResponseCode(ctx, LoginConstants.STATUS_LOGIN_SERVER_REJECTED_SESSION);
return;
}
byte[] val = new byte[length];
payload.readBytes(val);
BigInteger value = new BigInteger(val);
value = value.modPow(NetworkConstants.RSA_EXPONENT, NetworkConstants.RSA_MODULUS);
ByteBuf secure = Unpooled.wrappedBuffer(value.toByteArray());
int id = secure.readUnsignedByte();
if (id != 10) {
logger.fine("Unable to read id from secure payload.");
writeResponseCode(ctx, LoginConstants.STATUS_LOGIN_SERVER_REJECTED_SESSION);
return;
}
long clientSeed = secure.readLong();
long reportedSeed = secure.readLong();
if (reportedSeed != serverSeed) {
logger.fine("Reported seed differed from server seed.");
writeResponseCode(ctx, LoginConstants.STATUS_LOGIN_SERVER_REJECTED_SESSION);
return;
}
int uid = secure.readInt();
String username = BufferUtil.readString(secure);
String password = BufferUtil.readString(secure);
InetSocketAddress socketAddress = (InetSocketAddress) ctx.channel().remoteAddress();
String hostAddress = InetAddresses.toAddrString(socketAddress.getAddress());
// if (password.length() < 6 || password.length() > 20 || username.isEmpty() || username.length() > 12) {
// logger.fine("Username ('" + username + "') or password did not pass validation.");
// writeResponseCode(ctx, LoginConstants.STATUS_INVALID_CREDENTIALS);
// return;
// }
int[] seed = new int[4];
seed[0] = (int) (clientSeed >> 32);
seed[1] = (int) clientSeed;
seed[2] = (int) (serverSeed >> 32);
seed[3] = (int) serverSeed;
IsaacRandom decodingRandom = new IsaacRandom(seed);
for (int index = 0; index < seed.length; index++) {
seed[index] += 50;
}
IsaacRandom encodingRandom = new IsaacRandom(seed);
PlayerCredentials credentials = new PlayerCredentials(username, password, usernameHash, uid, hostAddress);
IsaacRandomPair randomPair = new IsaacRandomPair(encodingRandom, decodingRandom);
out.add(new LoginRequest(credentials, randomPair, reconnecting, lowMemory, release, crcs, version));
}
}
/**
* Writes a response code to the client and closes the current channel.
*
* @param ctx The context of the channel handler.
* @param response The response code to write.
*/
private void writeResponseCode(ChannelHandlerContext ctx, int response) {
ByteBuf buffer = ctx.alloc().buffer(Byte.BYTES);
buffer.writeByte(response);
ctx.writeAndFlush(buffer).addListener(ChannelFutureListener.CLOSE);
}
}